<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,user-scalable=no,minimum-scale=1.0,maximum-scale=1.0"/>
    <title>Tun Editor</title>

    <script src="quill.min.js" charset="utf-8"></script>
    <script src="quill.mention.min.js" charset="utf-8"></script>
    <script src="quill.markdown2.min.js" charset="utf-8"></script>
    <script>
      let u = navigator.userAgent;
      let isAndroid = u.indexOf('Android') > -1 || u.indexOf('Adr') > -1;
      let isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/);
      // let isAndroid = false;
      // let isIOS = false;
      let bridge = {
        onTextChange: function(delta, oldDelta, source) {
          if (isAndroid) {
            tun.onTextChange(delta, oldDelta, source);
          } else if (isIOS) {
            window.webkit.messageHandlers.onTextChange.postMessage({
               delta: delta,
               oldDelta: oldDelta,
               source: source,
            });
          } else {
            console.log(JSON.stringify(delta));
            console.log(JSON.stringify(oldDelta));
          }
        },
        onSelectionChange: function(index, length, format) {
          if (isAndroid) {
            tun.onSelectionChange(index, length, format);
          } else if (isIOS) {
            window.webkit.messageHandlers.onSelectionChange.postMessage({
               index: index,
               length: length,
               format: format,
            });
          }
        },
        onMentionClick: function(id, prefixChar, text) {
          if (isAndroid) {
            tun.onMentionClick(id, prefixChar, text);
          } else if (isIOS) {
            window.webkit.messageHandlers.onMentionClick.postMessage({
               id: id,
               prefixChar: prefixChar,
               text: text,
            });
          } else {
            console.log('on mention click', id, prefixChar, text);  
          }
        },
        onLinkClick: function(url) {
          if (isAndroid) {
            tun.onLinkClick(url);
          } else if (isIOS) {
            window.webkit.messageHandlers.onLinkClick.postMessage({
               url: url,
            });
          }
        },
        onFocusChange: function(hasFocus) {
          if (isAndroid) {
            tun.onFocusChange(hasFocus);
          } else if (isIOS) {
            window.webkit.messageHandlers.onFocusChange.postMessage({
               hasFocus: hasFocus,
            });
          }
        },
        loadImage: function(path) {
          if (isAndroid) {
            return tun.loadImage(path);
          } else if (isIOS) {
            return window.webkit.messageHandlers.loadImage.postMessage(path);
          }
        },
        loadVideoThumb: function(path) {
          if (isAndroid) {
            return tun.loadVideoThumb(path);
          } else if (isIOS) {
            return window.webkit.messageHandlers.loadVideoThumb.postMessage(path);
          }
        },
      }

      let replaceText;
      let setContents;
      let updateContents;
      let format;
      let formatText;
      let setSelection;
      let focus;
      let blur;
      let setPlaceholder;
      let setReadOnly;
      let setPadding;
      let refreshImage;
      let refreshVideoThumb;
      let setImageStyle;
      let setVideoStyle;
      let setPlaceholderStyle;
      let setupMarkdownSyntax;

      let imageStyle = {};
      let videoStyle = {};

      const messages = [];
      let isRunning = false;
      let processMessage;
      let sleep;
      let pushMessage;
      let startMessageHandler;

      document.addEventListener('DOMContentLoaded', () => {
        let Parchment = Quill.import('parchment');
        let Delta = Quill.import('delta');
        let Keyboard = Quill.import('modules/keyboard');
        let Block = Quill.import('blots/block');
        let BlockEmbed = Quill.import('blots/block/embed');
        let Container = Quill.import('blots/container');
        let Embed = Quill.import('blots/embed');
        let Inline = Quill.import('blots/inline');

        class Link extends Inline {
          static create(value) {
            const node = super.create(value);
            node.setAttribute('href', value);
            node.setAttribute('rel', 'noopener noreferrer');
            node.setAttribute('target', '_blank');
            return node;
          }

          static formats(domNode) {
            return domNode.getAttribute('href');
          }

          static sanitize(url) {
            const anchor = document.createElement('a');
            anchor.href = url;
            const protocol = anchor.href.slice(0, anchor.href.indexOf(':'));
            return protocols.indexOf(protocol) > -1;
            return sanitize(url, this.PROTOCOL_WHITELIST) ? url : this.SANITIZED_URL;
          }

          format(name, value) {
            if (name !== this.statics.blotName || !value) {
              super.format(name, value);
            } else {
              this.domNode.setAttribute('href', this.constructor.sanitize(value));
            }
          }
        }
        Link.blotName = 'link';
        Link.tagName = 'A';
        Link.SANITIZED_URL = 'about:blank';
        Link.PROTOCOL_WHITELIST = ['http', 'https', 'mailto', 'tel'];
        Quill.register(Link, true);

        class DividerBlot extends Embed {

          static create(type) {
            let node = super.create();
            node.setAttribute('type', type);
            return node;
          }

          static value(node) {
            return node.getAttribute('type');
          }

        }
        DividerBlot.blotName = 'divider';
        DividerBlot.tagName = 'hr';
        Quill.register(DividerBlot, true);

        class ImageExtend extends Embed {
          static create(value) {
            const node = super.create(value);
            const source = value.source || value.src;
            let url = this.sanitize(source);
            if (url.startsWith('file')) {
              bridge.loadImage(url.replace('file://', ''));
            } else {
              node.setAttribute('src', url);
            }
            node.onerror = function() {
              node.setAttribute('src', 'common_lost.svg');
            }

            // Set default style.
            if (imageStyle.width) {
              node.setAttribute('width', imageStyle.width);
            }
            if (imageStyle.height) {
              node.setAttribute('height', imageStyle.height);
            }
            if (imageStyle.align) {
              ImageExtend.align(node, imageStyle.align);
            }

            Object.keys(value).forEach(function(key) {
              node.dataset[key] = value[key];
            });
            node.addEventListener('click', function(event) {
              let blot = Quill.find(node);
              let index = blot.offset(quill.scroll);
              quill.setSelection(index + 1, 0, Quill.sources.API)
            });
            return node;
          }

          static match(url) {
            return /\.(jpe?g|gif|png)$/.test(url) || /^data:image\/.+;base64/.test(url);
          }

          static register() {
            if (/Firefox/i.test(navigator.userAgent)) {
              setTimeout(() => {
                // Disable image resizing in Firefox
                document.execCommand('enableObjectResizing', false, false);
              }, 1);
            }
          }

          static sanitize(url) {
            let protocols = ['http', 'https', 'mailto', 'tel', 'file'];
            const anchor = document.createElement('a');
            anchor.href = url;
            const protocol = anchor.href.slice(0, anchor.href.indexOf(':'));
            if (protocol === 'file' && !url.startsWith('file://')) {
              return `file://${url}`;
            }
            let isValid = protocols.indexOf(protocol) > -1;
            return isValid ? url : '//:0';
          }

          static value(domNode) {
            return domNode.dataset;
          }

          static formats(domNode) {
            // return ['width', 'height', 'align'].reduce((formats, attribute) => {
            //   if (domNode.hasAttribute(attribute)) {
            //     formats[attribute] = domNode.getAttribute(attribute);
            //   }
            //   return formats;
            // }, {});
            return {};
          }

          format(name, value) {
            if (['width', 'height'].indexOf(name) > -1) {
              if (value) {
                this.domNode.setAttribute(name, value);
              } else {
                this.domNode.removeAttribute(name);
              }
            } else if (name === 'align') {
              ImageExtend.align(this.domNode, value);
            } else {
              super.format(name, value);
            }
          }

          static align(node, position) {
            if (position === 'center') {
              node.style.margin = '0 auto';
            } else if (position === 'left') {
              node.style.margin = '0';
            } else if (position === 'right') {
              node.style.margin = '0 0 0 auto';
            } else {
              node.style.margin = '0';
            }
          }

        }
        ImageExtend.blotName = 'image';
        ImageExtend.tagName = 'IMG';
        Quill.register(ImageExtend, true);

        class VideoBlot extends Embed {

          static create(value) {
            const node = super.create(value);

            const video = document.createElement("DIV");
            video.className = 'ql-video-content'
            video.setAttribute('contenteditable', false);

            const poster = document.createElement("IMG");
            let posterUrl = this.sanitize(value.thumbUrl);
            if (posterUrl.startsWith('file')) {
              bridge.loadVideoThumb(posterUrl.replace('file://', ''));
            } else {
              poster.setAttribute('src', posterUrl);
            }

            const durationSpan = document.createElement("div");
            durationSpan.className = 'video-duration-mask';
            if (value.duration) {
              let minute = parseInt(value.duration / 60);
              let second = parseInt(value.duration % 60);
              if (minute > 99) {
                minute = 99;
              }
              if (second > 99) {
                second = 99;
              }
              let timeFormat = `${minute.toString().padStart(2, '0')}:${second.toString().padStart(2, '0')}`
              durationSpan.innerHTML = `${timeFormat}`;
            } else {
              durationSpan.innerHTML = '00:00';
            }
            durationSpan.setAttribute('contenteditable', false);

            const removeSpan = document.createElement("div");
            removeSpan.className = 'video-remove-mask';
            removeSpan.innerHTML = `<svg t="1642472221263" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6896" width="12" height="12"><path d="M225.386667 165.056L512 451.626667 798.613333 165.056a21.333333 21.333333 0 0 1 28.16-1.792l2.005334 1.792 30.165333 30.165333a21.333333 21.333333 0 0 1 0 30.165334L572.330667 512l286.613333 286.613333a21.333333 21.333333 0 0 1 0 30.165334l-30.165333 30.165333a21.333333 21.333333 0 0 1-30.165334 0L512 572.330667 225.386667 858.944a21.333333 21.333333 0 0 1-28.16 1.792l-2.005334-1.792-30.165333-30.165333a21.333333 21.333333 0 0 1 0-30.165334L451.626667 512 165.056 225.386667a21.333333 21.333333 0 0 1 0-30.165334L195.2 165.056a21.333333 21.333333 0 0 1 30.165333 0z" fill="#ffffff" p-id="6897"></path></svg>`;
            removeSpan.setAttribute('contenteditable', false);

            video.appendChild(poster);
            video.appendChild(durationSpan);
            video.appendChild(removeSpan);
            node.appendChild(video);

            if (videoStyle.width) {
              node.style.width = `${videoStyle.width}px`;
            }
            if (videoStyle.height) {
              node.style.height = `${videoStyle.height}px`;
            }
            if (videoStyle.align) {
              VideoBlot.align(node, videoStyle.align);
            }

            Object.keys(value).forEach(function(key) {
              node.dataset[key] = value[key];
            });
            removeSpan.addEventListener('click', function(event) {
              let blot = Quill.find(node);
              let index = blot.offset(quill.scroll);
              quill.updateContents(new Delta()
                .retain(index)
                .delete(1)
              );
              event.stopPropagation();
            });
            node.addEventListener('click', function(event) {
              console.log('on click');
              let blot = Quill.find(node);
              let index = blot.offset(quill.scroll);
              quill.setSelection(index + 1, 0, Quill.sources.API)
            });
            return node;
          }

          static sanitize(url) {
            let protocols = ['http', 'https', 'mailto', 'tel', 'blob', 'file'];
            const anchor = document.createElement('a');
            anchor.href = url;
            const protocol = anchor.href.slice(0, anchor.href.indexOf(':'));
            if (protocol === 'file' && !url.startsWith('file://')) {
              return `file://${url}`;
            }
            let isValid = protocols.indexOf(protocol) > -1;
            return isValid ? url: 'about:blank';
          }

          static value(domNode) {
            return domNode.dataset;
          }

          static formats(domNode) {
            // return ['width', 'height'].reduce((formats, attribute) => {
            //   if (domNode.style.hasOwnProperty(attribute)) {
            //     formats[attribute] = domNode.style[attribute].replace('px', '');
            //   }
            //   return formats;
            // }, {});
            return {};
          }

          static align(node, position) {
            if (position === 'center') {
              node.style.margin = '0 auto';
            } else if (position === 'left') {
              node.style.margin = '0';
            } else if (position === 'right') {
              node.style.margin = '0 0 0 auto';
            } else {
              node.style.margin = '0';
            }
          }

          format(name, value) {
            if (['width', 'height'].indexOf(name) > -1) {
              if (value) {
                this.domNode.style[name] = `${value}px`;
              } else {
                this.domNode.style[name] = null;
              }
            } else {
              super.format(name, value);
            }
          }

        }
        VideoBlot.blotName = 'video';
        VideoBlot.className = 'ql-video';
        VideoBlot.tagName = 'DIV';
        Quill.register(VideoBlot, true);

        class CodeBlockLine extends Block {

          static formats(domNode) {
            return domNode.className === this.className ? undefined : super.formats(domNode);
          }

          format(name, value) {
            if (name === CodeBlock.blotName && !value) {
              this.replaceWith(Parchment.create(this.statics.scope));
            } else {
              // super.format();
            }
          }

          remove() {
            if (this.prev == null && this.next == null) {
              this.parent.remove();
            } else {
              super.remove();
            }
          }

          replaceWith(name, value) {
            this.parent.isolate(this.offset(this.parent), this.length());
            if (name === this.parent.statics.blotName) {
              this.parent.replaceWith(name, value);
              return this;
            } else {
              this.parent.unwrap();
              return super.replaceWith(name, value);
            }
          }
        }
        CodeBlockLine.blotName = 'code-block-line';
        CodeBlockLine.className = 'ql-code-block';
        CodeBlockLine.tagName = 'DIV';
        Quill.register(CodeBlockLine, true);

        class CodeBlock extends Container {
          static create(value) {
            let tagName = value === 'ordered' ? 'OL' : 'UL';
            let node = super.create(tagName);
            node.setAttribute('spellcheck', false);
            return node;
          }

          static formats(domNode) {
            if (domNode.className === this.className) return true;
            return undefined;
          }

          optimize(context) {
            super.optimize(context);
            let next = this.next;
            if (next != null && next.prev === this &&
                next.statics.blotName === this.statics.blotName &&
                next.domNode.tagName === this.domNode.tagName &&
                next.domNode.getAttribute('data-checked') === this.domNode.getAttribute('data-checked')) {
              next.moveChildren(this);
              next.remove();
            }
          }

          replace(target) {
            if (target.statics.blotName !== this.statics.blotName) {
              let item = Parchment.create(this.statics.defaultChild);
              target.moveChildren(item);
              this.appendChild(item);
            }
            super.replace(target);
          }

          formats() {
            return { [this.statics.blotName]: this.statics.formats(this.domNode) };
          }

        }
        CodeBlock.blotName = 'code-block';
        CodeBlock.scope = Parchment.Scope.BLOCK_BLOT;
        CodeBlock.tagName = 'DIV';
        CodeBlock.className = 'ql-code-block-container';
        CodeBlock.defaultChild = 'code-block-line';
        CodeBlock.allowedChildren = [CodeBlockLine];
        Quill.register(CodeBlock, true);

        let bindings = {
          'code exit new': {
            key: 'Enter',
            collapsed: true,
            format: ['code-block'],
            offset: 0,
            suffix: /^$/,
            // suffix: /^\s*$/,
            handler: function(range, context) {
              const [line, offset] = this.quill.getLine(range.index);
              const isCodeBlock = line.domNode.className === 'ql-code-block';
              if (!isCodeBlock) {
                return true;
              }
              const isNextCodeBlock = line.next !== null && line.next.domNode.className === 'ql-code-block'
              if (isNextCodeBlock) {
                return true;
              }
              this.quill.format('code-block', false, Quill.sources.USER);
            }
          },
          'code delete': {
            key: 'Backspace',
            collapsed: true,
            format: ['code-block'],
            offset: 0,
            suffix: /^$/,
            handler: function(range, context) {
              const [line, offset] = this.quill.getLine(range.index);
              const isCodeBlock = line.domNode.className === 'ql-code-block';
              if (!isCodeBlock) {
                return true;
              }
              const isPrevCodeBlock = line.prev !== null && line.prev.domNode.className === 'ql-code-block'
              if (isPrevCodeBlock) {
                return true;
              }
              const isNextCodeBlock = line.next !== null && line.next.domNode.className === 'ql-code-block'
              if (isNextCodeBlock) {
                return true;
              }
              this.quill.format('code-block', false, Quill.sources.USER);
            }
          },
          'blockquote delete': {
            key: 'Backspace',
            collapsed: true,
            format: ['blockquote'],
            prefix: /^$/,
            suffix: /^$/,
            handler: function(range) {
              this.quill.format('blockquote', false, Quill.sources.USER);
            }
          },
          'header delete': {
            key: 'Backspace',
            collapsed: true,
            format: ['header'],
            prefix: /^$/,
            suffix: /^$/,
            handler: function(range) {
              this.quill.format('header', false, Quill.sources.USER);
            }
          },
          'enter to new line': {
            key: 'Enter',
            collapsed: true,
            format: ['bold', 'italic', 'underline', 'strike'],
            handler: function(range) {
              this.quill.format('bold', false);
              this.quill.format('italic', false);
              this.quill.format('underline', false);
              this.quill.format('strike', false);
              this.quill.insertText(range.index, '\n', Quill.sources.USER);
              this.quill.setSelection(range.index + 1, Quill.sources.USER);
            }
          }
        }
        var options = {
          readOnly: false,
          theme: 'snow',
          modules: {
            syntax: false,
            toolbar: false,
            keyboard: {
              bindings: bindings
            },
            mention: {
              source: function(searchTerm, renderList, mentionChar) {
              }
            },
          }
        };
        var quill = new Quill('#editor', options);
        quill.root.addEventListener('click', (ev) => {
          if (ev.target.tagName === 'A') {
            // Link click.
            bridge.onLinkClick(ev.target.href);
            ev.preventDefault();
          }

          // Mention click.
          if (ev.target.tagName === 'SPAN') {
            if (ev.target.className === 'mention') {
              let id = ev.target.attributes['data-id'].value;
              let prefixChar = ev.target.attributes['data-prefix-char'].value;
              let value = ev.target.attributes['data-value'].value;
              let blot = Quill.find(ev.target);
              let index = blot.offset(quill.scroll);
              quill.setSelection(index + 1, 0, Quill.sources.API)
              bridge.onMentionClick(id, prefixChar, value);
            } else if (ev.target.className === 'ql-mention-denotation-char') {
              let mentionNode = ev.target.parentElement.parentElement;
              let id = mentionNode.attributes['data-id'].value;
              let prefixChar = mentionNode.attributes['data-prefix-char'].value;
              let value = mentionNode.attributes['data-value'].value;
              let blot = Quill.find(mentionNode);
              let index = blot.offset(quill.scroll);
              quill.setSelection(index + 1, 0, Quill.sources.API)
              bridge.onMentionClick(id, prefixChar, value);
            } else if (ev.target.attributes['contenteditable']
                  && ev.target.attributes['contenteditable'].value === 'false'
                  && ev.target.parentElement.className === 'mention') {
              let mentionNode = ev.target.parentElement;
              let id = mentionNode.attributes['data-id'].value;
              let prefixChar = mentionNode.attributes['data-prefix-char'].value;
              let value = mentionNode.attributes['data-value'].value;
              let blot = Quill.find(mentionNode);
              let index = blot.offset(quill.scroll);
              quill.setSelection(index + 1, 0, Quill.sources.API)
              bridge.onMentionClick(id, prefixChar, value);
            }
          }
        });
        let lastSizeChangeTime = 0;
        let lastTextChangeTime = 0;
        let lastFocusTime = 0;
        let lastScrollTop = 0;
        quill.root.addEventListener('scroll', function(event) {
          const now = new Date();
          if (now.getTime() - lastSizeChangeTime < 1000
                || now.getTime() - lastTextChangeTime < 1000
                || now.getTime() - lastFocusTime < 1000) {
            return;
          }
          const scrollTop = event.target.scrollTop
          if (lastScrollTop - scrollTop > 20
                && quill.getSelection() !== null
                && quill.getSelection().length <= 0
                && quill.hasFocus()) {
            quill.blur();
          }
          lastScrollTop = scrollTop;
        });
        quill.root.addEventListener('blur', function() {
          bridge.onFocusChange(false);
        });
        quill.root.addEventListener('focus', function() {
          const now = new Date();
          lastFocusTime = now.getTime();
          bridge.onFocusChange(true);
        });
        quill.root.addEventListener('compositionstart', function() {
          quill.root.classList.toggle('ql-blank', false);
        });
        quill.root.addEventListener('compositionend', function() {
          quill.root.classList.toggle('ql-blank', quill.editor.isBlank());
        });
        window.addEventListener('resize', function() {
          const now = new Date();
          lastSizeChangeTime = now.getTime();
          quill.scrollIntoView();
        });

        quill.on('text-change', function(delta, oldDelta, source) {
          const now = new Date();
          lastTextChangeTime = now.getTime();

          if (source === Quill.sources.SILENT) {
            return;
          }
          bridge.onTextChange(JSON.stringify(delta), JSON.stringify(oldDelta), source);

          setTimeout(function() {
            let range = quill.getSelection(true);
            if (range) {
              var format = quill.getFormat();
              bridge.onSelectionChange(range.index, range.length, JSON.stringify(format));
            }
          }, 200);
        });
        quill.on('selection-change', function(range, oldRange, source) {
          if (range) {
            var format = quill.getFormat()
            bridge.onSelectionChange(range.index, range.length, JSON.stringify(format));
          }
        });
        quill.clipboard.addMatcher(Node.ELEMENT_NODE, (node, delta) => {
          console.info(node, delta);
          let ops = []
          delta.ops.forEach(op => {
            if (op.insert && op.insert["video"] && typeof op.insert["video"] == 'object' && !op.insert["video"]["thumb_url"]) {
              return;
            } else if (op.insert && op.insert["image"] && typeof op.insert["image"] == 'object' && !op.insert["image"]["source"] && !op.insert["image"]["src"]) {
              return;
            }
            ops.push(op)
          })
          delta.ops = ops
          return delta;
        })

        replaceText = function(index, length, data, attributes,
            newLineAfterImage, isEmbeddable, ignoreFocus, selection) {
          if (isEmbeddable) {
            var delta = new Delta().retain(index).delete(length).insert(data, attributes)
            if (Object.keys(data).length > 0
                    && Object.keys(data)[0] === 'image'
                    && newLineAfterImage) {
              delta = delta.insert('\n');
            }
            updateContents(delta, Quill.sources.API, ignoreFocus, selection);
          } else {
            let delta = new Delta().retain(index).delete(length).insert(data, attributes)
            updateContents(delta, Quill.sources.API, ignoreFocus, selection);
          }
        }
        setContents = function(delta) {
          quill.setContents(delta, Quill.sources.SILENT);
        }
        updateContents = function(delta, source, ignoreFocus, selection) {
          pushMessage({
            name: 'updateContents',
            delta: delta,
            source: source,
            ignoreFocus: ignoreFocus,
            selection: selection,
          })
        }
        format = function(name, value) {
          quill.format(name, value, 'user');
        }
        formatText = function(index, length, format, value) {
          quill.formatText(index, length, format, value, Quill.sources.USER);
        }
        setSelection = function(index, length, ignoreFocus) {
          if (!quill.hasFocus() && ignoreFocus) {
            let isEnable = quill.root.getAttribute('contenteditable') === 'true'
            quill.disable();
            quill.setSelection(index, length, Quill.sources.API)
            quill.enable(isEnable);
          } else {
            quill.setSelection(index, length, Quill.sources.API)
          }
        }
        focus = function() {
          quill.focus();
        }
        blur = function() {
            quill.blur();
        }
        setPlaceholder = function(placeholder) {
          quill.root.dataset.placeholder = placeholder;
        }
        setReadOnly = function(readOnly) {
          quill.enable(!readOnly);
        }
        setPadding = function(top, right, bottom, left) {
          var editor = document.querySelector('.ql-editor');
          editor.style.padding = `${top}px ${right}px ${bottom}px ${left}px`;
        }
        refreshImage = function(filename, data) {
          var image = document.querySelectorAll(`[data-source="${filename}"]`);
          if (image.length === 0) {
            image = document.querySelectorAll(`[data-source="file://${filename}"]`);
          }
          if (image.length === 0) {
            console.log('not found image element', path);
            return;
          }
          for (const i of image) {
            i.setAttribute('src', data);
          }
        }
        refreshVideoThumb = function(filename, data) {
          var video = document.querySelectorAll(`[data-thumb-url="${filename}"]`);
          if (video.length === 0) {
            video = document.querySelectorAll(`[data-thumb-url="file://${filename}"]`);
          }
          if (video.length === 0) {
            console.log('not found video thumb element', path);
            return;
          }

          for (const v of video) {
            const posterNode = v.querySelector('img');
            posterNode.setAttribute('src', data)
          }
          // fetch(data)
          //   .then(response => response.blob())
          //   .then(blob => {
          //     for (const v of video) {
          //       const videoNode = v.querySelector('video');
          //       videoNode.poster = URL.createObjectURL(blob);
          //     }
          //   });
        }
        setImageStyle = function(style) {
          imageStyle = style;
        }
        setVideoStyle = function(style) {
          videoStyle = style;
        }
        setPlaceholderStyle = function(style) {
          let headChildren = document.head.children;
          for (let ele of headChildren) {
            if (ele.id === 'custom') {
              document.head.removeChild(ele);
              break;
            }
          }
          let inlineStyle = document.createElement('style');
          document.head.appendChild(inlineStyle);
          inlineStyle.id = 'custom';
          let sheet = inlineStyle.sheet;
          for (k in style) {
            sheet.addRule('.ql-editor.ql-blank::before', `${k}: ${style[k]}`);
          }
        }
        setupMarkdownSyntax = function(enableMarkdownSyntax) {
          if (enableMarkdownSyntax) {
            var markdownOptions = {
              ignoreTags: [ 'h4', 'h5', 'h6' ],
            };
            new QuillMarkdown(quill, markdownOptions);
          }
        }
        processMessage = function(message) {
          return new Promise(function(resolve, reject) {
            if (message.name === 'updateContents') {
              if (!quill.hasFocus() && message.ignoreFocus) {
                let isEnable = quill.root.getAttribute('contenteditable') === 'true'
                quill.disable();
                quill.updateContents(message.delta, message.source)
                if (message.selection.index >= 0) {
                  quill.setSelection(message.selection.index, message.selection.length);
                }
                setTimeout(function() {
                  quill.enable(isEnable)
                  resolve();
                }, 300);
              } else {
                quill.updateContents(message.delta, message.source)
                const range = quill.getSelection();
                if (message.selection.index >= 0) {
                  quill.setSelection(message.selection.index, message.selection.length);
                }
                resolve();
              }
            }
          });
        }
        sleep = function(time) {
          return new Promise(function(resolve, reject) {
            setTimeout(function() {
              resolve();
            }, time);
          });
        }
        pushMessage = function(message) {
          messages.push(message)
          if (!isRunning) {
            startMessageHandler();
          }
        }
        startMessageHandler = async function() {
          isRunning = true;
          while (isRunning) {
            if (messages.length > 0) {
              const message = messages.shift();
              await processMessage(message);
            } else {
              isRunning = false;
            }
          }
        }

        // replaceText(0, 0, {
        //   mention: {
        //     'denotationChar': '',
        //     'id': 'id xxx',
        //     'value': '@Jeffrey *Wu*',
        //     'prefixChar': '@',
        //   },
        // }, {}, false, true, false, { index: -1, length: -1 });
        // replaceText(0, 0, {
        //   image: {
        //     "name": "72955931-ccc07900-3d52-11ea-89b1-d468a6e2aa2b.png",
        //     "source": "https://user-images.githubusercontent.com/122956/72955931-ccc07900-3d52-11ea-89b1-d468a6e2aa2b.png",
        //     "width": 330.0,
        //     "height": 330.0,
        //     "checkPath": "https://user-images.githubusercontent.com/122956/72955931-ccc07900-3d52-11ea-89b1-d468a6e2aa2b.png",
        //     "_type": "image",
        //     "_inline": false
        //   }
        // }, false, true);
        // replaceText(0, 0, {
        //   video: {
        //     "source": "https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_20mb.mp4",
        //     "width": 360.0,
        //     "height": 720.0,
        //     "fileType": "video",
        //     "duration": 7,
        //     "thumbUrl": "https://fb-cdn.fanbook.mobi/fanbook/app/files/chatroom/image/43789ea4452106628661d9014d45c873.jpg",
        //     "thumbName": "43789ea4452106628661d9014d45c873.jpg",
        //     "_type": "video",
        //     "_inline": false
        //   }
        // }, { width: 230, height: 460 }, false, true, false, { index: -1, length: -1 });
      })
    </script>

    <link rel="stylesheet" href="normalize.css" type="text/css" charset="utf-8">
    <link rel="stylesheet" href="snow.css" type="text/css" charset="utf-8">
    <link rel="stylesheet" href="quill.custom.css" type="text/css" charset="utf-8">
  </head>
  <body>
    <div id="editor"></div>
  </body>
</html>
